#include <string.h>

#include "lib.h"
#include "memorystream.h"
using namespace Library;

#define SUPER	Stream
#define SELF	MemoryStream


//----------------------------------------------------------------------------
// Private structures
//

/**
 *
 */
struct MemoryStream::InodeTable {
	uint8 * page[ Library::MemoryStream::PAGES ];
};

/**
 *
 */
struct MemoryStream::BlockTable {
	InodeTable * inodeTable[ Library::MemoryStream::INODES ];
};

//----------------------------------------------------------------------------
// Public
//

/**
 * Creates an empty in-memory stream.
 */
MemoryStream::MemoryStream()
	: SUPER()
{
	m_size		= 0;
	m_offset	= 0;
	memset(m_superblock, 0, sizeof m_superblock);
}

MemoryStream::~MemoryStream()
{
	clean();
}

int32
MemoryStream::read(void * buffer, int32 length)
{
	uint8 *	page;
	uint8 *	dest;
	int32	offset;
	int32	copied;
	int32	size;
	int32	blockNumber;
	int32	inodeNumber;
	int32	pageNumber;
	int32	pageOffset;

	// Reading beyond the size of the stream is *not* allowed.
	if (!buffer || length < 0 || m_offset >= m_size)
		return eof;

	// Zero length reads are allowed.
	if (length == 0)
		return 0;

	// Limit the requested size (length) to the amount of data available.
	if (m_offset + length > m_size)
		length = m_size - m_offset;

	offset	= m_offset;
	dest	= (uint8 *)buffer;
	copied	= 0;

	while (length)
	{
		decodeOffset(offset, &blockNumber, &inodeNumber, &pageNumber,
			&pageOffset);

		// This handles partial-initial, whole, and partial-final reads.
		size = MIN(PAGE_SIZE - pageOffset, length);

		// An unfound page means a non-sequential write occurred (and we
		// didn't allocate all the zero initialized pages in the middle -
		// to save space), so we pretend as if they were initialized and
		// copy zeros to the user's buffer.
		page = retrievePage(blockNumber, inodeNumber, pageNumber, false);
		if (page)
			memcpy(dest, page + pageOffset, size);
		else
			memset(dest, 0, size);

		offset	+= size;
		dest	+= size;
		copied	+= size;
		length	-= size;
	}

	m_offset = offset;

	return copied;
}

int32
MemoryStream::write(void * buffer, int32 length)
{
	uint8 *	page;
	uint8 *	source;
	int32	offset;
	int32	copied;
	int32	size;
	int32	blockNumber;
	int32	inodeNumber;
	int32	pageNumber;
	int32	pageOffset;

	// Writing beyond the size of the stream *is* allowed (which initializes
	// the data in the middle to zero).
	if (!buffer || length < 0)
		return eof;

	// Zero length writes are allowed.
	if (length == 0)
		return 0;

	offset	= m_offset;
	source	= (uint8 *)buffer;
	copied	= 0;

	while (length)
	{
		decodeOffset(offset, &blockNumber, &inodeNumber, &pageNumber,
			&pageOffset);

		// This handles partial-initial, whole, and partial-final writes.
		size = MIN(PAGE_SIZE - pageOffset, length);

		// An unfound page means we suffered an allocation failure.  We
		// handle this error by returning a short write.
		page = retrievePage(blockNumber, inodeNumber, pageNumber, true);
		if (!page)
			break;

		memcpy(page + pageOffset, source,  size);

		offset	+= size;
		source	+= size;
		copied	+= size;
		length	-= size;
	}

	m_offset = offset;
	if (m_offset > m_size)
		m_size = m_offset;

	return copied;
}

int32
MemoryStream::seek(int32 offset, SeekFrom from)
{
	switch (from)
	{
		case beginning:
			if (0 + offset < 0)
				return eof;

			m_offset = 0 + offset;
			break;

		case current:
			if (m_offset + offset < 0)
				return eof;

			m_offset = m_offset + offset;
			break;

		case end:
			if (m_size + offset < 0)
				return eof;

			m_offset = m_size + offset;
			break;

		default:
			return eof;
	}

	return m_offset;
}

int32
MemoryStream::tell()
{
	return m_offset;
}

//----------------------------------------------------------------------------
// Protected
//

/**
 * This method deletes all in-memory buffers and resets the stream to zero.
 */
void
MemoryStream::clean()
{
	BlockTable *	blockTable;
	InodeTable *	inodeTable;
	uint8 *			page;
	int32			blockNumber;
	int32			inodeNumber;
	int32			pageNumber;

	for (blockNumber = 0; blockNumber < BLOCKS; blockNumber++)
	{
		blockTable = m_superblock[blockNumber];
		if (!blockTable)
			continue;

		for (inodeNumber = 0; inodeNumber < INODES; inodeNumber++)
		{
			inodeTable = blockTable->inodeTable[inodeNumber];
			if (!inodeTable)
				continue;

			for (pageNumber = 0; pageNumber < PAGES; pageNumber++)
			{
				page = inodeTable->page[pageNumber];
				if (!page)
					continue;

				delete [] page;
			}

			delete inodeTable;
		}

		delete blockTable;
		m_superblock[blockNumber] = 0;
	}

	m_size		= 0;
	m_offset	= 0;
}

/**
 * @param offset	...
 */
void
MemoryStream::decodeOffset(int32 offset, int32 * blockNumber,
	int32 * inodeNumber, int32 * pageNumber, int32 * pageOffset)
{
	*blockNumber = (offset & 0x7e000000) >> 25;
	*inodeNumber = (offset & 0x01f80000) >> 19;
	*pageNumber  = (offset & 0x0007e000) >> 13;
	*pageOffset  = (offset & 0x00001fff) >>  0;
}

/**
 *
 */
uint8 *
MemoryStream::retrievePage(int32 blockNumber, int32 inodeNumber,
	int32 pageNumber, bool8 create)
{
	BlockTable *	blockTable;
	InodeTable *	inodeTable;
	uint8 *			page;

	blockTable = m_superblock[blockNumber];
	if (!blockTable && create)
	{
		if( create )
		{
			blockTable = new BlockTable;
		}

		if (!create || !blockTable )
			return 0;

		memset(blockTable, 0, sizeof BlockTable);
		m_superblock[blockNumber] = blockTable;
	}

	inodeTable = blockTable->inodeTable[inodeNumber];
	if (!inodeTable)
	{
		if( create )
		{
			inodeTable = new InodeTable;
		}

		if (!create || !inodeTable)
			return 0;

		memset(inodeTable, 0, sizeof InodeTable);
		blockTable->inodeTable[inodeNumber] = inodeTable;
	}

	page = inodeTable->page[pageNumber];
	if (!page)
	{
		if( create )
		{
			page = new uint8[ PAGE_SIZE ];
		}

		if (!create || !page )
			return 0;

		memset(page, 0, PAGE_SIZE);
		inodeTable->page[pageNumber] = page;
	}
	
	return page;
}
